iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 13
2
Mobile Development

Android TDD 測試驅動開發系列 第 13

Day13 - 使用Robolectric 撰寫 Android test

  • 分享至 

  • xImage
  •  

到目前為止,我們在Android已經寫了這幾種測試。

Unit 測試:在JVM上執行的單元測試。
Instrumented 測試:與Android framework相依,需在模擬器或實機上執行測試。
UI 測試:一樣要在模擬器或實機上測試,UI測試更注重使用者的互動。

Instrumented 測試與UI測試都需要在模擬器或實機上執行,執行起來很花時間。這篇要介紹的是Robolectric,讓可以讓我們用單元測試的方式來執行Android Tests,也就是可以在JVM上執行測試,大大的提升了執行測試的效率。

延續上一個註冊的範例。這次我們要用Robolectric單元測試的方式來驗證跟View有關的:

  1. 當註冊失敗,則Alert「註冊失敗」
  2. 當註冊成功,則StartActivity 開啟註冊成功頁

Production code 如下

if (!isLoginIdOK) {
    // 註冊失敗,資料填寫錯誤
    val builder = AlertDialog.Builder(this)
    builder.setMessage("帳號至少要6碼,第1碼為英文").setTitle("錯誤")
    builder.show()
} else if (!isPwdOK) {
    val builder = AlertDialog.Builder(this)
    builder.setMessage("密碼至少要8碼,第1碼為英文,並包含1碼數字").setTitle("錯誤")
    builder.show()
} else {
    //註冊成功,儲存Id
    Repository(this).saveUserId(loginId)

    val intent = Intent(this, ResultActivity::class.java)
    intent.putExtra("ID", loginId)

    startActivity(intent)
}

我們將加上這幾個測試。

  1. 帳號輸入錯誤,需Alert
  2. 密碼輸入錯誤,需Alert
  3. 註冊成功,需StartActivity 至註冊成功頁

使用Robolectric

buide.gralde
加上testOptions.unitTests.includeAndroidResources = true

android{
    testOptions.unitTests.includeAndroidResources = true
}

加上robolectric 元件

dependencies { 
    testImplementation "org.robolectric:robolectric:4.3"
}

開始測試

新增MainActivityTest,因為是在JVM執行,測試程式要放在test的目錄,而不是androidTest

在測試程式的setup使用Robolectric來初始化MainActivity

private lateinit var activity: MainActivity

@Before
fun setupActivity() {
    MockitoAnnotations.initMocks(this)

    activity = Robolectric.buildActivity(MainActivity::class.java).setup().get()
}

新增一個測試,驗證註冊成功是否有開啟ResultActivity

@Test
fun registerSuccessShouldDirectToResult() {
    
}

要知道有沒有使用startActivity開啟指定的Activity,需要建立一個ShadowActivity,將用來觀察是否有開啟別的Activity

@Test
fun registerSuccessShouldDirectToResult() {
    val shadowActivity = Shadows.shadowOf(activity)
}

在loginId, password 輸入欄位,放入可以通過註冊驗證的值,確保待會執行測試會是走註冊成功的流程

@Test
fun registerSuccessShouldDirectToResult() {
    //arrange
    val shadowActivity = Shadows.shadowOf(activity)

    val userId = "A123456789"
    val userPassword = "a123456789"

    activity.loginId.setText(userId)
    activity.password.setText(userPassword)
}

開始驗證

點下註冊按鈕,驗證是否有開啟ResultActivity,要開啟一個Activity需要使用Intent。所以這裡要驗證的重點其實是Intent的資料是否正確。

1.Intent的目的地的Activity
2.Intent的size
3.Intent傳送的key與value

@Test
fun registerSuccessShouldDirectToResult() {

    …

    //點下註冊按鈕
    activity.send.performClick()

    //驗證註冊成功時,是否有開啟ResultActivity
    val nextIntent = shadowActivity.nextStartedActivity
    assertEquals(nextIntent.component!!.className, ResultActivity::class.java.name)
    assertEquals(1, nextIntent.extras!!.size())
    assertEquals(userId, nextIntent.extras!!.getString("ID"))
}

測試註冊失敗應Alert

要測試註冊失敗有沒有跳出Alert時,可以使用 ShadowAlertDialog.getLatestDialog() 來進行驗證。

@Test
fun registerFailShouldAlert() {
    
    //arrange
    val userId = "A1234"
    val userPassword = "a123456789"

    activity.loginId.setText(userId)
    activity.password.setText(userPassword)

    //點下註冊按鈕
    activity.send.performClick()

    val dialog = ShadowAlertDialog.getLatestDialog()

    //Assert
    assertNotNull(dialog)
    assertTrue(dialog.isShowing)
}

Robolectric讓我們像單元測試一樣的測試與Android UI元件的互動。

我們曾提過一個好的單元測試應具備**獨立(Independent)**的特性。這裡的測試存在一個問題,當send.setOnClickListener被觸發時,會呼叫RegisterVerify().isLoginIdVerify 檢查帳號是否正確,那如果這個功能壞掉了呢?那是不是在這個測試我想驗證的註冊成功是否startActivity就沒被驗證到了。

https://ithelp.ithome.com.tw/upload/images/20190927/20111896Gq2SKU3Pvo.png

這裡給大家一個練習,讓大家可以先想看看怎麼解決。我們在介紹MVP、MVVM時,再來說明怎麼把View的處理再獨立出來。解決這個問題。

範例下載:
https://github.com/evanchen76/RobolectricSample

參考
http://robolectric.org/

小技巧
Shift + command + ; 列出最近執行的測試


上一篇
Day12 - UI 測試:使用Espresso
下一篇
Day14 - 使用Custom View Components提升可測試性
系列文
Android TDD 測試驅動開發30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言